﻿/*
   Created 2009 December 13, Randall Maas
   Copyright (c) 2009, Randall Maas
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:

   Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

   Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

   Neither the name of Blackwood Designs, LLC nor the names of its contributors
   may be used to endorse or promote products derived from this software without
   specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
   THE POSSIBILITY OF SUCH DAMAGE.
*/

using System ;
using System . Drawing;
using System . Windows . Forms;

/// <summary>
/// This class exists to help me separate the video graphs used here
/// </summary>
partial class VideoController
{
   /// <summary>
   /// An internal dummy used to refer to the movies audio track
   /// </summary>
   internal static DirectShowNode _MovieAudio = new DirectShowNode("..Movie audio..");

   /// <summary>
   /// The source of audio
   /// </summary>
   internal DirectShowNode  _AudioSource;

   /// <summary>
   /// The video input source
   /// </summary>
   VideoSource     _VideoSource;

   /// <summary>
   /// This is used to flip the video frames before further processing
   /// </summary>
   FlipVideo       Flip;

   /// <summary>
   /// This is used to get the overexposed light painted area
   /// </summary>
   LightPaint      PaintedArea;

   /// <summary>
   /// This is the camera graph.  It is never paused
   /// </summary>
   DirectShowGraph CamVideo=null;


   /// <summary>
   /// This is the desired out going movie
   /// </summary>
   DirectShowGraph RecVideo=null;

   /// <summary>
   /// The node for the recording preview.  It is available so we can resize the frame
   /// </summary>
   DirectShowNode RecPreview =null;

   /// <summary>
   /// Whether to flip the camera image or not
   /// </summary>
   bool _FlipHorizontal;


   /// <summary>
   /// An optional background image
   /// </summary>
   internal Bitmap BkgndBasePict = null;

   /// <summary>
   /// The encoding profile to use
   /// </summary>
   internal IWMProfile EncProfile;


   /// <summary>
   /// The target size of the video
   /// </summary>
   internal int TgtWidth = 640;

   /// <summary>
   /// The name of the input file
   /// </summary>
   string _InFileName = null;

   ~VideoController()
   {
      Dispose();
   }

   public void Dispose()
   {
      GC . SuppressFinalize(this);
      Stop();
      if (null != _AudioSource)
        _AudioSource . Dispose();
      if (null != _VideoSource)
        _VideoSource . Dispose();
   }

   #region Configuration
   /// <summary>
   /// This is called when we change which camera to use
   /// </summary>
   /// <param name="Source"></param>
   public void SetSource(VideoSource Source)
   {
      if (Source == _VideoSource)
        return;
      Stop();
      if (null != _VideoSource)
        _VideoSource . Dispose();

      _VideoSource = Source;
   }


   /// <summary>
   /// This is called when we change which audio source to use
   /// </summary>
   /// <param name="Source"></param>
   public void SetSource(DirectShowNode Source)
   {
      if (Source == _AudioSource)
        return;
      Stop();
      if (null != _AudioSource)
        _AudioSource . Dispose();

      _AudioSource = Source;
   }


   /// <summary>
   /// Accesses the name of the file for the background
   /// </summary>
   public string InFileName
   {
      get {return _InFileName;}
      set
      {
         _InFileName = value;
         BkgndBasePict = null;
         if (null != value)
         try
         {
            BkgndBasePict = new Bitmap(_InFileName);
         }
         catch
         {
         }
         if (null != PaintedArea && null != RecVideo)
           if (null != BkgndBasePict)
             PaintedArea . SetBackground(new Bitmap(BkgndBasePict, PaintedArea . Size));
            else
             PaintedArea . SetBackground(null);
      }
   }

   public bool FlipHorizontal
   {
      get {return _FlipHorizontal;}
      set
      {
         _FlipHorizontal = value;
         if (null == Flip)
           return ;
         // Distribute the flip to the things that work with that
         Flip . FlipHorizontal = _FlipHorizontal;
//         PaintedArea . FlipBuffer();
      }
   }


   /// <summary>
   /// Sets the threshold for being considered a light
   /// </summary>
   /// <param name="Value"></param>
   public void Threshold(int Value)
   {
       if (null != PaintedArea)
         PaintedArea . Threshold = Value ;
   }
   #endregion


   #region The Control Methods
   /// <summary>
   /// Stop any recording or other video graph
   /// </summary>
   public void Stop()
   {
      if (null != RecVideo)
        {
           RecVideo . Stop();
           RecVideo . Dispose();
           RecVideo = null;
           RecPreview=null;
        }
      PaintedArea = null;
      if (null == CamVideo)
        return ;
      CamVideo . Stop();
      CamVideo . Dispose();
      CamVideo = null;
   }

   public void Pause()
   {
      if (null != RecVideo)
        RecVideo . Pause();
   }

   public void Start()
   {
      if (null != RecVideo)
        RecVideo . Start();
   }

   public void ResizePreview(Control C)
   {
      if (null != RecPreview)
        RecPreview . PreviewResize(C);
   }

   public void ConfigVideoSource(IntPtr Handle)
   {
      if (null != _VideoSource)
       _VideoSource . PropertyPage(Handle);
   }


   public void ClearPen()
   {
      if (null != PaintedArea)
        {
        PaintedArea . Swap();
        }
   }
   #endregion

   /// <summary>
   /// This is a helper used to slect which media type to grab
   /// </summary>
   /// <param name="MediaType"></param>
   /// <returns></returns>
   public int RankMediaType(AMMediaType MediaType)
   {
      // First, only accept RGB24 forms
      if (MediaType . SubType != DirectShowNode . RGB24)
        return -1;

      Size S = MediaType . Size;
      int diff = (TgtWidth - S.Width);
      diff *= diff;
      return 1638400-diff;
   }

   #region Build the Video Graphs
   /// <summary>
   /// This is used to graph of the pen scanner, preview, etc.
   /// </summary>
   /// <param name="CamPreview">The control to render the frame on</param>
   public void BuildPreviewGraph(Control CamPreview)
   {
      // Disable any face tracking
      _VideoSource . FaceTracking  = PluralMode . None;

      // Add the camera source
      CamVideo = new DirectShowGraph();
      CamVideo . Add(_VideoSource, "source", null);

      // Add the flip video item, as a delegate of a sample grabber
      SampleGrabber CamFrameGrabber1 = new SampleGrabber();
      Flip = new FlipVideo();
      CamFrameGrabber1 . Callback(Flip);
      Flip . FlipHorizontal = FlipHorizontal;
      AMMediaType  Media = CamVideo . BestMediaType(RankMediaType);
      CamVideo . Add(CamFrameGrabber1, "flipgrabber",  Media);
      CamFrameGrabber1 . MediaType = Media;

      // Add the paint-with light item, as a delegate of a sample grabber
      SampleGrabber CamFrameGrabber = new SampleGrabber();
      PaintedArea = new LightPaint();
      CamFrameGrabber . Callback(PaintedArea);
      Media = CamVideo . BestMediaType(RankMediaType);
      CamVideo . Add(CamFrameGrabber, "grabber",  Media);
      CamFrameGrabber . MediaType = Media;

      DirectShowNode Preview = new DirectShowNode(DirectShowNode . CLSID_VideoRenderer);
      CamVideo . Add(Preview, "render1", null);

      Preview . RenderOnto(CamPreview);

      // Add a null renderer to consume any extra pins from the camera source
      DirectShowNode N = new DirectShowNode(DirectShowNode.CLSID_NULLRenderer);
      CamVideo . Add(N, "null",null);

      // The size isn't known until we've built a sample grabber graph
      CamFrameGrabber1 . UpdateFrameSize();
      Flip . Size = CamFrameGrabber1 . FrameSize;
      CamFrameGrabber . UpdateFrameSize();
      PaintedArea . Size = CamFrameGrabber . FrameSize;

      // Start the camera graph
      CamVideo . Start();
   }


   /// <summary>
   /// Record what is on the camera -- plus an optional background -- to a movie
   /// </summary>
   public void BuildCamcorderGraph(string OutFileName, Control CamPreview, Control VidPreview)
   {
      // Disable any face tracking
      _VideoSource . FaceTracking  = PluralMode . None;

      // Begin setting up the graph
      RecVideo = new DirectShowGraph();
      // Add in the sound
      if (null != _AudioSource)
        RecVideo . Add(_AudioSource,"sound", null);

      // Add in the video source
      RecVideo . Add(_VideoSource, "source", null);

      // Add the flip video item, as a delegate of a sample grabber
      SampleGrabber CamFrameGrabber1 = new SampleGrabber();
      Flip = new FlipVideo();
      CamFrameGrabber1 . Callback(Flip);
      Flip . FlipHorizontal = FlipHorizontal;
      AMMediaType  Media = RecVideo . BestMediaType(RankMediaType);
      RecVideo . Add(CamFrameGrabber1, "flipgrabber",  Media);
      CamFrameGrabber1 . MediaType = Media;

      // Split the video so we can record something without the previous doodle
      // -- I don't think we can sample grab a preview pin; atleast it doesn't work for me
      DirectShowNode Tee = new DirectShowNode(DirectShowNode . CLSID_SmartTee);
      RecVideo . Add(Tee, "split1", null);

      // Grab the frame (initially)
      SampleGrabber _CamFrameGrabber = new SampleGrabber();
      PaintedArea = new LightPaint();
      _CamFrameGrabber . Callback(PaintedArea);
      Media = RecVideo . BestMediaType(RankMediaType);
      RecVideo . Add(_CamFrameGrabber, "grabber1",  Media);
      _CamFrameGrabber . MediaType = Media;

      // Split the video so we can preview what will be recorded
      Tee = new DirectShowNode(DirectShowNode . CLSID_SmartTee);
      RecVideo . Add(Tee, "split2", null);


      // Add a video encoder to write to a file
      WMAsfWriter S = new WMAsfWriter();
      S . Path = OutFileName;
      RecVideo . Add (S, "video saver", Media);
      S . Profile = EncProfile;

      // Add a video renderer to preview what will be written to disk
      RecPreview = new DirectShowNode(DirectShowNode . CLSID_VideoRenderer);
      RecVideo . Add(RecPreview, "render2", null);
      RecPreview . RenderOnto (VidPreview);

      // Add the overlay on the preview
      OverlayPoints OP = new OverlayPoints();
      OP . SrcPoints = PaintedArea;
      Media = RecVideo . BestMediaType(RankMediaType);
      // Grab the frame (for overlay)
      SampleGrabber _CamFrameGrabber2 = new SampleGrabber();
      _CamFrameGrabber2 . Callback(OP);
      _CamFrameGrabber2 . MediaType = Media;
      RecVideo . Add(_CamFrameGrabber2, "grabber2",  Media);
      _CamFrameGrabber2 . MediaType = Media;


      DirectShowNode CPreview = new DirectShowNode(DirectShowNode . CLSID_VideoRenderer);
      RecVideo . Add(CPreview, "render1", null);
      CPreview . RenderOnto (CamPreview);

      // Add a null renderer to consume any extra pins from the camera source
      DirectShowNode N = new DirectShowNode(DirectShowNode.CLSID_NULLRenderer);
      RecVideo . Add(N, "null",null);

      // The size isn't known until we've built a sample grabber graph
      CamFrameGrabber1 . UpdateFrameSize();
      Flip . Size = CamFrameGrabber1 . FrameSize;
      _CamFrameGrabber . UpdateFrameSize();
      PaintedArea . Size = _CamFrameGrabber . FrameSize;
      if (null != BkgndBasePict)
        PaintedArea . SetBackground(new Bitmap(BkgndBasePict, PaintedArea . Size));
      _CamFrameGrabber2 . UpdateFrameSize();
      OP . Size = _CamFrameGrabber2 . FrameSize;

      // Start the camera graph
      RecVideo . Start();
   }


   public void RebuildGraph(string OutFileName, Control CamPreview, Control VidPreview)
   {
      if (null == _VideoSource)
        return;
      // Stop any use of the webcam
      Stop ();

      if (null != BkgndBasePict || null == InFileName)
        {
           BuildCamcorderGraph(OutFileName, CamPreview, VidPreview);
           return ;
        }

      // Disable any face tracking
      _VideoSource . FaceTracking  = PluralMode . None;

      BuildPreviewGraph(CamPreview);
      RecVideo = new DirectShowGraph();

      WMAsfReader W = new WMAsfReader();
      W . Path = InFileName;
      RecVideo . Add(W, "source", null);

      DirectShowNode ColorConverter = new DirectShowNode(DirectShowNode . CLSID_Colour);
      RecVideo . Add(ColorConverter, "convert",null);

      OverlayPoints OP = new OverlayPoints();
      OP . SrcPoints = PaintedArea;
      AMMediaType  Media = RecVideo . BestMediaType(RankMediaType);
      // Grab the frame (initially)
      SampleGrabber _CamFrameGrabber = new SampleGrabber();
      _CamFrameGrabber . Callback(OP);
      _CamFrameGrabber . MediaType = Media;
      RecVideo . Add(_CamFrameGrabber, "grabber1",  Media);
      _CamFrameGrabber . MediaType = Media;

      // Add a tee to split the frame grabber into a part we can save
      // and a part we can watch
      DirectShowNode Tee = new DirectShowNode(DirectShowNode . CLSID_SmartTee);
      RecVideo . Add(Tee, "split", Media);

      // Add in the sound
      if (null != _AudioSource && _AudioSource != _MovieAudio)
        RecVideo . Add(_AudioSource,"sound", null);

      // Add file saver
      WMAsfWriter S = new WMAsfWriter();
      S . Path = OutFileName;
      RecVideo . Add (S, "video saver", Media);
      S . Profile = EncProfile;

      RecVideo . AutoShow = false;
      RecVideo . RenderOnto(VidPreview);

      // The size isn't known until we've built a sample grabber graph
      _CamFrameGrabber . UpdateFrameSize();
      OP . Size = _CamFrameGrabber . FrameSize;

      RecVideo . Start();
   }

   #endregion

}